from typing import Dict, List, Optional, Union
from datetime import timedelta, datetime
from mg_app_framework import get_logger
from common.basic_rabbitmq import send_rabbitmq
from common.constant import (BomInfo, MaterialConfig, RetrospectNode,
                             RetrospectParams, RetrospectType, WorkOrderConfig, CodeTypeEnum)
from common.retrospect_operate.material_tools import (get_material_code,
                                                      get_work_order_code)
from common.retrospect_operate.work_order_config import get_work_order_config

__all__ = ["filter_qr_code_bom_config", "filter_material_bom_config", "publish_retrospect_nodes",
           "filter_device_code_workorder_config", "filter_device_bom_config"]


class RetrospectOperateHandel:
    def __init__(self, device_code: str):
        self.device_code = device_code
        self.work_order_config: Optional[WorkOrderConfig] = None

    async def init_self(self):
        self.work_order_config = await get_work_order_config(self.device_code)

    def get_bom_config(self) -> BomInfo:
        """
        获取BOM配置
        :return:
        """
        return self.work_order_config.bom_info

    def get_bom_product_in(self) -> List[MaterialConfig]:
        """
        获取BOM的投入配置
        """
        return self.work_order_config.bom_info.product_in or []

    def get_bom_product_out(self) -> List[MaterialConfig]:
        """
        获取BOM的产出配置
        """
        return self.work_order_config.bom_info.product_out or []

    def get_material_bom_config(
            self,
            *,
            material_code: str,
            op_type: str,
    ) -> Optional[MaterialConfig]:
        """
        获取物料的指定BOM配置
        :param material_code:
        :param op_type:
        :return:
        """
        material_config: Optional[MaterialConfig] = None
        if op_type == RetrospectType.PRODUCT_IN:
            pd_config = self.get_bom_product_in()
        else:
            pd_config = self.get_bom_product_out()
        for n in pd_config:
            if n.material_code == material_code:
                material_config = n
                break
        return material_config

    async def build_retrospect_node(
            self,
            *,
            qr_code: str,
            material_code: str,
            work_order_code: str,
            op_type: str,
            ng_list: List = None,
            check_result: str = "",
            op_time: Optional[datetime] = None,
            material_quota: Union[int, float, None] = None,
            **kwargs,
    ) -> RetrospectNode:
        if not material_code:
            raise ValueError(f"二维码:{qr_code}对应的物料编码为空!")
        material_config = self.get_material_bom_config(
            material_code=material_code, op_type=op_type
        )
        if not material_config:
            op_name = "投入" if op_type == RetrospectType.PRODUCT_IN else "产出"
            raise ValueError(f"物料编码:{material_code}对应的BOM{op_name}配置为空!")

        if not work_order_code and material_config.code_type == CodeTypeEnum.batch_code and op_type == RetrospectType.PRODUCT_IN:
            raise ValueError(f"批次码:{qr_code}作为投入物料, 需要指定子工单编码!")
        if not work_order_code:
            raise ValueError(f"二维码:{qr_code}对应的工单编码为空!")

        return self._build_retrospect_node(
            qr_code=qr_code,
            material_code=material_code,
            workorder_code=work_order_code,
            op_type=op_type,
            material_config=material_config,
            ng_list=ng_list,
            check_result=check_result,
            op_time=op_time,
            material_quota=material_quota
        )

    def _build_retrospect_node(
            self,
            *,
            qr_code: str,
            material_code: str,
            workorder_code: str,
            op_type: str,
            material_config: MaterialConfig,
            ng_list: list = None,
            check_result: str = "",
            op_time: Optional[datetime] = None,
            material_quota: Union[int, float, None] = None
    ) -> RetrospectNode:
        return RetrospectNode(
            **{
                "workorder_code": workorder_code,
                "raw_workorder_code": self.work_order_config.workorder_code,
                "procedure_code": self.work_order_config.procedure_code,
                "production_type": self.work_order_config.workorder_type,
                "produce_unit_code": self.work_order_config.procedure_code,
                "process_section": self.work_order_config.process_section,
                "device_code": self.device_code,
                "production_model": self.work_order_config.production_model,
                "material_info": f"{material_code}~{qr_code}",
                "qr_code": qr_code,
                "ng_list": ng_list,
                "check_result": check_result,
                "code_index": 0,
                "op_type": op_type,
                "material_type": material_config.material_type,
                "material_code": material_config.material_code,
                "code_type": material_config.code_type,
                "require_num": material_config.material_quota if not material_quota else material_quota,
                "op_time": datetime.now() if not op_time else op_time
            }
        )


async def filter_device_bom_config(
        device_code: str,
) -> Optional[BomInfo]:
    """
    获取当前物料的BOM配置
    :param device_code: 设备编码
    :return:
    """
    retrospect_handle = RetrospectOperateHandel(device_code=device_code)
    await retrospect_handle.init_self()
    return retrospect_handle.get_bom_config()


async def filter_qr_code_bom_config(
        device_code: str, *, qr_code: str, op_type: str
) -> Optional[MaterialConfig]:
    """
    获取当前物料的BOM配置
    :param device_code: 设备编码
    :param qr_code: 二维码
    :param op_type: 投产类型
    :return:
    """
    retrospect_handle = RetrospectOperateHandel(device_code=device_code)
    await retrospect_handle.init_self()
    material_code = await get_material_code(qr_code, op_type, retrospect_handle.work_order_config)
    return retrospect_handle.get_material_bom_config(material_code=material_code, op_type=op_type)


async def filter_device_code_workorder_config(device_code: str) -> Optional[WorkOrderConfig]:
    """
    获取当前设备编码的BOM配置
    :param device_code: 设备编码
    :return:
    """
    retrospect_handle = RetrospectOperateHandel(device_code=device_code)
    await retrospect_handle.init_self()
    work_order_config = retrospect_handle.work_order_config
    return work_order_config


async def filter_material_bom_config(
        device_code: str, *, material_code: str, op_type: str
) -> Optional[MaterialConfig]:
    """
    获取当前物料的BOM配置
    :param device_code: 设备编码
    :param material_code: 物料编码
    :param op_type: 投产类型
    :return:
    """
    retrospect_handle = RetrospectOperateHandel(device_code=device_code)
    await retrospect_handle.init_self()
    return retrospect_handle.get_material_bom_config(material_code=material_code, op_type=op_type)


async def publish_retrospect_nodes(
        device_code: str, *, retrospect_items: List[Dict]
) -> List[RetrospectNode]:
    """
    发布一次投产的工单输入信息(原则上需要调用方确定, 不支持多个产品同时投入追溯数据)
    :param device_code: 设备编码
    :param retrospect_items: 待处理的回溯项，包含二维码、操作类型等信息
    :raises ValueError: 当二维码重复或操作类型不合法时抛出异常
    """
    if not retrospect_items:
        raise ValueError(f"设备:{device_code}, 传入追溯节点信息为空!")
    valid_items = RetrospectParams.validate_items(retrospect_items)

    all_type_qr_codes = set()
    for item in valid_items:
        ck_key = f"{item.qr_code}~{item.op_type}"
        if ck_key in all_type_qr_codes:
            raise ValueError(f"设备:{device_code}, 二维码:{item.qr_code} 类型:{item.op_type} 重复添加,请校验!")
        all_type_qr_codes.add(ck_key)

    for item in valid_items:
        if item.op_type not in RetrospectType.all_types():
            raise ValueError(f"设备:{device_code}, 投产类型异常:{item.op_type},请校验!")

    work_order_set = set()
    for item in valid_items:
        if item.work_order_code:
            work_order_set.add(item.work_order_code)
    if len(work_order_set) > 1:
        raise ValueError(f"构建追溯列表中传入了多个工单:{work_order_set}信息，请校验输入!")
    retrospect_handle = RetrospectOperateHandel(device_code=device_code)
    await retrospect_handle.init_self()

    for item in valid_items:
        if not item.material_code:
            item.material_code = await get_material_code(item.qr_code, item.op_type,
                                                         retrospect_handle.work_order_config)
        if not item.material_code:
            raise ValueError(f"二维码:{item.qr_code}对应的物料编码为空!")

    if work_order_set:
        prd_work_order_id = list(work_order_set)[0]
    else:
        prd_work_order_id = None
        for item in valid_items:
            tmp_material_config = retrospect_handle.get_material_bom_config(
                material_code=item.material_code,
                op_type=item.op_type)
            if not tmp_material_config:
                op_name = "投入" if item.op_type == RetrospectType.PRODUCT_IN else "产出"
                raise ValueError(f"物料编码:{item.material_code}对应的BOM{op_name}配置为空!")
            tmp_work_order_id = await get_work_order_code(item.qr_code, item.op_type,
                                                          retrospect_handle.work_order_config, tmp_material_config)
            if tmp_work_order_id:
                prd_work_order_id = tmp_work_order_id
                break
    if not prd_work_order_id:
        raise ValueError(f"投产数据:{retrospect_items}对应的工单编码为空!")
    for item in valid_items:
        item.work_order_code = prd_work_order_id
    retrospect_nodes: List[RetrospectNode] = []
    all_op_type = set()
    for item in valid_items:
        tmp_node = await retrospect_handle.build_retrospect_node(
            qr_code=item.qr_code,
            op_type=item.op_type,
            material_code=item.material_code,
            work_order_code=item.work_order_code,
            check_result=item.check_result,
            ng_list=item.ng_list,
            op_time=item.op_time,
            material_quota=item.material_quota
        )
        all_op_type.add(item.op_type)
        retrospect_nodes.append(tmp_node)
    # 如果投入和产出数据放在一起的话，为了避免时间一致，这里默认间隔3秒
    if len(all_op_type) > 1:
        for item in retrospect_nodes:
            if item.op_type == RetrospectType.PRODUCT_IN:
                item.op_time = item.op_time - timedelta(seconds=3)
    for item in retrospect_nodes:
        item.op_time = item.op_time.strftime("%Y-%m-%d %H:%M:%S.%f")[:-3]
    if retrospect_nodes:
        await send_rabbitmq(key="work_order_retrospect_operate",
                            data={"insert": [node.dict() for node in retrospect_nodes], "delete": []})

    else:
        get_logger().error(f"设备:{device_code}, 构建的投产关系列表为空, 请校验输入参数: {retrospect_items}")
    return retrospect_nodes
